#include "U2VertexIndexData.h"

#include "U2HardwareBufferManager.h"
#include "U2HardwareVertexBuffer.h"
#include "U2HardwareIndexBuffer.h"
#include "U2Exception.h"


U2EG_NAMESPACE_USING


//-----------------------------------------------------------------------
U2VertexData::U2VertexData(U2HardwareBufferManagerBase* mgr)
{
	mMgr = mgr ? mgr : U2HardwareBufferManager::getSingletonPtr();
	vertexBufferBinding = mMgr->createVertexBufferBinding();
	vertexDeclaration = mMgr->createVertexDeclaration();
	mDeleteDclBinding = true;
	vertexCount = 0;
	vertexStart = 0;
}
//---------------------------------------------------------------------
U2VertexData::U2VertexData(U2VertexDeclaration* dcl, U2VertexBufferBinding* bind)
{
	// this is a fallback rather than actively used
	mMgr = U2HardwareBufferManager::getSingletonPtr();
	vertexDeclaration = dcl;
	vertexBufferBinding = bind;
	mDeleteDclBinding = false;
	vertexCount = 0;
	vertexStart = 0;
}
//-----------------------------------------------------------------------
U2VertexData::~U2VertexData()
{
	if (mDeleteDclBinding)
	{
		mMgr->destroyVertexBufferBinding(vertexBufferBinding);
		mMgr->destroyVertexDeclaration(vertexDeclaration);
	}
}
//-----------------------------------------------------------------------
U2VertexData* U2VertexData::clone(bool copyData, U2HardwareBufferManagerBase* mgr) const
{
	U2HardwareBufferManagerBase* pManager = mgr ? mgr : mMgr;

	U2VertexData* dest = U2_NEW U2VertexData(mgr);

	// Copy vertex buffers in turn
	const U2VertexBufferBinding::VertexBufferBindingMap& bindings = 
		this->vertexBufferBinding->getBindings();
	U2VertexBufferBinding::VertexBufferBindingMap::const_iterator vbi, vbend;
	vbend = bindings.end();
	for (vbi = bindings.begin(); vbi != vbend; ++vbi)
	{
		U2HardwareVertexBufferSharedPtr srcbuf = vbi->second;
        U2HardwareVertexBufferSharedPtr dstBuf;
        if (copyData)
        {
		    // create new buffer with the same settings
		    dstBuf = pManager->createVertexBuffer(
				    srcbuf->getVertexSize(), srcbuf->getNumVertices(), srcbuf->getUsage(),
				    srcbuf->hasShadowBuffer());

		    // copy data
		    dstBuf->copyData(*srcbuf, 0, 0, srcbuf->getSizeInBytes(), true);
        }
        else
        {
            // don't copy, point at existing buffer
            dstBuf = srcbuf;
        }

		// Copy binding
		dest->vertexBufferBinding->setBinding(vbi->first, dstBuf);
    }

    // Basic vertex info
    dest->vertexStart = this->vertexStart;
	dest->vertexCount = this->vertexCount;
    // Copy elements
    const U2VertexDeclaration::VertexElementList elems = 
        this->vertexDeclaration->getElements();
    U2VertexDeclaration::VertexElementList::const_iterator ei, eiend;
    eiend = elems.end();
    for (ei = elems.begin(); ei != eiend; ++ei)
    {
        dest->vertexDeclaration->addElement(
            ei->getSource(),
            ei->getOffset(),
            ei->getType(),
            ei->getSemantic(),
            ei->getIndex() );
    }

    return dest;
}
//-----------------------------------------------------------------------
void U2VertexData::reorganiseBuffers(U2VertexDeclaration* newDeclaration, 
	const BufferUsageList& bufferUsages, U2HardwareBufferManagerBase* mgr)
{
	U2HardwareBufferManagerBase* pManager = mgr ? mgr : mMgr;
    // Firstly, close up any gaps in the buffer sources which might have arisen
    newDeclaration->closeGapsInSource();

	// Build up a list of both old and new elements in each buffer
	unsigned short buf = 0;
    std::vector<void*> oldBufferLocks;
    std::vector<size_t> oldBufferVertexSizes;
	std::vector<void*> newBufferLocks;
    std::vector<size_t> newBufferVertexSizes;
	U2VertexBufferBinding* newBinding = pManager->createVertexBufferBinding();
    const U2VertexBufferBinding::VertexBufferBindingMap& oldBindingMap = vertexBufferBinding->getBindings();
    U2VertexBufferBinding::VertexBufferBindingMap::const_iterator itBinding;

    // Pre-allocate old buffer locks
    if (!oldBindingMap.empty())
    {
        size_t count = oldBindingMap.rbegin()->first + 1;
        oldBufferLocks.resize(count);
        oldBufferVertexSizes.resize(count);
    }
	// Lock all the old buffers for reading
    for (itBinding = oldBindingMap.begin(); itBinding != oldBindingMap.end(); ++itBinding)
    {
        assert(itBinding->second->getNumVertices() >= vertexCount);

        oldBufferVertexSizes[itBinding->first] =
            itBinding->second->getVertexSize();
        oldBufferLocks[itBinding->first] =
            itBinding->second->lock(
                U2HardwareBuffer::HBL_READ_ONLY);
    }
	
	// Create new buffers and lock all for writing
	buf = 0;
	while (!newDeclaration->findElementsBySource(buf).empty())
	{
        size_t vertexSize = newDeclaration->getVertexSize(buf);

		U2HardwareVertexBufferSharedPtr vbuf = 
			pManager->createVertexBuffer(
				vertexSize,
				vertexCount, 
				bufferUsages[buf]);
		newBinding->setBinding(buf, vbuf);

        newBufferVertexSizes.push_back(vertexSize);
		newBufferLocks.push_back(
			vbuf->lock(U2HardwareBuffer::HBL_DISCARD));
		buf++;
	}

	// Map from new to old elements
    typedef std::map<const U2VertexElement*, const U2VertexElement*> NewToOldElementMap;
	NewToOldElementMap newToOldElementMap;
	const U2VertexDeclaration::VertexElementList& newElemList = newDeclaration->getElements();
	U2VertexDeclaration::VertexElementList::const_iterator ei, eiend;
	eiend = newElemList.end();
	for (ei = newElemList.begin(); ei != eiend; ++ei)
	{
		// Find corresponding old element
		const U2VertexElement* oldElem = 
			vertexDeclaration->findElementBySemantic(
				(*ei).getSemantic(), (*ei).getIndex());
		if (!oldElem)
		{
			// Error, cannot create new elements with this method
			U2_EXCEPT(U2Exception::ERR_ITEM_NOT_FOUND, 
				"Element not found in old vertex declaration", 
				"U2VertexData::reorganiseBuffers");
		}
		newToOldElementMap[&(*ei)] = oldElem;
	}
	// Now iterate over the new buffers, pulling data out of the old ones
	// For each vertex
	for (size_t v = 0; v < vertexCount; ++v)
	{
		// For each (new) element
		for (ei = newElemList.begin(); ei != eiend; ++ei)
		{
			const U2VertexElement* newElem = &(*ei);
            NewToOldElementMap::iterator noi = newToOldElementMap.find(newElem);
			const U2VertexElement* oldElem = noi->second;
			unsigned short oldBufferNo = oldElem->getSource();
			unsigned short newBufferNo = newElem->getSource();
			void* pSrcBase = static_cast<void*>(
				static_cast<unsigned char*>(oldBufferLocks[oldBufferNo])
				+ v * oldBufferVertexSizes[oldBufferNo]);
			void* pDstBase = static_cast<void*>(
				static_cast<unsigned char*>(newBufferLocks[newBufferNo])
				+ v * newBufferVertexSizes[newBufferNo]);
			void *pSrc, *pDst;
			oldElem->baseVertexPointerToElement(pSrcBase, &pSrc);
			newElem->baseVertexPointerToElement(pDstBase, &pDst);
			
			memcpy(pDst, pSrc, newElem->getSize());
		}
	}

	// Unlock all buffers
    for (itBinding = oldBindingMap.begin(); itBinding != oldBindingMap.end(); ++itBinding)
    {
        itBinding->second->unlock();
    }
    for (buf = 0; buf < newBinding->getBufferCount(); ++buf)
    {
        newBinding->getBuffer(buf)->unlock();
    }

	// Delete old binding & declaration
	if (mDeleteDclBinding)
	{
		pManager->destroyVertexBufferBinding(vertexBufferBinding);
		pManager->destroyVertexDeclaration(vertexDeclaration);
	}

	// Assign new binding and declaration
	vertexDeclaration = newDeclaration;
	vertexBufferBinding = newBinding;		
	// after this is complete, new manager should be used
	mMgr = pManager;
	mDeleteDclBinding = true; // because we created these through a manager

}
//-----------------------------------------------------------------------
void U2VertexData::reorganiseBuffers(U2VertexDeclaration* newDeclaration, U2HardwareBufferManagerBase* mgr)
{
    // Derive the buffer usages from looking at where the source has come
    // from
    BufferUsageList usages;
    for (unsigned short b = 0; b <= newDeclaration->getMaxSource(); ++b)
    {
        U2VertexDeclaration::VertexElementList destElems = newDeclaration->findElementsBySource(b);
        // Initialise with most restrictive version 
        // (not really a usable option, but these flags will be removed)
        U2HardwareBuffer::Usage final = static_cast<U2HardwareBuffer::Usage>(
            U2HardwareBuffer::HBU_STATIC_WRITE_ONLY | U2HardwareBuffer::HBU_DISCARDABLE);
        U2VertexDeclaration::VertexElementList::iterator v;
        for (v = destElems.begin(); v != destElems.end(); ++v)
        {
            U2VertexElement& destelem = *v;
            // get source
            const U2VertexElement* srcelem =
                vertexDeclaration->findElementBySemantic(
                    destelem.getSemantic(), destelem.getIndex());
            // get buffer
            U2HardwareVertexBufferSharedPtr srcbuf = 
                vertexBufferBinding->getBuffer(srcelem->getSource());
            // improve flexibility only
            if (srcbuf->getUsage() & U2HardwareBuffer::HBU_DYNAMIC)
            {
                // remove static
                final = static_cast<U2HardwareBuffer::Usage>(
                    final & ~U2HardwareBuffer::HBU_STATIC);
                // add dynamic
                final = static_cast<U2HardwareBuffer::Usage>(
                    final | U2HardwareBuffer::HBU_DYNAMIC);
            }
            if (!(srcbuf->getUsage() & U2HardwareBuffer::HBU_WRITE_ONLY))
            {
                // remove write only
                final = static_cast<U2HardwareBuffer::Usage>(
                    final & ~U2HardwareBuffer::HBU_WRITE_ONLY);
            }
            if (!(srcbuf->getUsage() & U2HardwareBuffer::HBU_DISCARDABLE))
            {
                // remove discardable
                final = static_cast<U2HardwareBuffer::Usage>(
                    final & ~U2HardwareBuffer::HBU_DISCARDABLE);
            }
            
        }
        usages.push_back(final);
    }
    // Call specific method
    reorganiseBuffers(newDeclaration, usages, mgr);

}
//-----------------------------------------------------------------------
void U2VertexData::closeGapsInBindings(void)
{
    if (!vertexBufferBinding->hasGaps())
        return;

    // Check for error first
    const U2VertexDeclaration::VertexElementList& allelems = 
        vertexDeclaration->getElements();
    U2VertexDeclaration::VertexElementList::const_iterator ai;
    for (ai = allelems.begin(); ai != allelems.end(); ++ai)
    {
        const U2VertexElement& elem = *ai;
        if (!vertexBufferBinding->isBufferBound(elem.getSource()))
        {
            U2_EXCEPT(U2Exception::ERR_ITEM_NOT_FOUND,
                "No buffer is bound to that element source.",
                "U2VertexData::closeGapsInBindings");
        }
    }

    // Close gaps in the vertex buffer bindings
    U2VertexBufferBinding::BindingIndexMap bindingIndexMap;
    vertexBufferBinding->closeGaps(bindingIndexMap);

    // Modify vertex elements to reference to new buffer index
    unsigned short elemIndex = 0;
    for (ai = allelems.begin(); ai != allelems.end(); ++ai, ++elemIndex)
    {
        const U2VertexElement& elem = *ai;
        U2VertexBufferBinding::BindingIndexMap::const_iterator it =
            bindingIndexMap.find(elem.getSource());
        assert(it != bindingIndexMap.end());
        u2ushort targetSource = it->second;
        if (elem.getSource() != targetSource)
        {
            vertexDeclaration->modifyElement(elemIndex, 
                targetSource, elem.getOffset(), elem.getType(), 
                elem.getSemantic(), elem.getIndex());
        }
    }
}
//-----------------------------------------------------------------------
void U2VertexData::removeUnusedBuffers(void)
{
    std::set<u2ushort> usedBuffers;

    // Collect used buffers
    const U2VertexDeclaration::VertexElementList& allelems = 
        vertexDeclaration->getElements();
    U2VertexDeclaration::VertexElementList::const_iterator ai;
    for (ai = allelems.begin(); ai != allelems.end(); ++ai)
    {
        const U2VertexElement& elem = *ai;
        usedBuffers.insert(elem.getSource());
    }

    // Unset unused buffer bindings
    u2ushort count = vertexBufferBinding->getLastBoundIndex();
    for (u2ushort index = 0; index < count; ++index)
    {
        if (usedBuffers.find(index) == usedBuffers.end() &&
            vertexBufferBinding->isBufferBound(index))
        {
            vertexBufferBinding->unsetBinding(index);
        }
    }

    // Close gaps
    closeGapsInBindings();
}
//-----------------------------------------------------------------------
void U2VertexData::convertPackedColour(
	VertexElementType srcType, VertexElementType destType)
{
	if (destType != VET_COLOUR_ABGR && destType != VET_COLOUR_ARGB)
	{
		U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS,
			"Invalid destType parameter", "U2VertexData::convertPackedColour");
	}
	if (srcType != VET_COLOUR_ABGR && srcType != VET_COLOUR_ARGB)
	{
		U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS,
			"Invalid srcType parameter", "U2VertexData::convertPackedColour");
	}

	const U2VertexBufferBinding::VertexBufferBindingMap& bindMap = 
		vertexBufferBinding->getBindings();
	U2VertexBufferBinding::VertexBufferBindingMap::const_iterator bindi;
	for (bindi = bindMap.begin(); bindi != bindMap.end(); ++bindi)
	{
		U2VertexDeclaration::VertexElementList elems = 
			vertexDeclaration->findElementsBySource(bindi->first);
		bool conversionNeeded = false;
		U2VertexDeclaration::VertexElementList::iterator elemi;
		for (elemi = elems.begin(); elemi != elems.end(); ++elemi)
		{
			U2VertexElement& elem = *elemi;
			if (elem.getType() == VET_COLOUR || 
				((elem.getType() == VET_COLOUR_ABGR || elem.getType() == VET_COLOUR_ARGB) 
				&& elem.getType() != destType))
			{
				conversionNeeded = true;
			}
		}

		if (conversionNeeded)
		{
			void* pBase = bindi->second->lock(U2HardwareBuffer::HBL_NORMAL);

			for (size_t v = 0; v < bindi->second->getNumVertices(); ++v)
			{

				for (elemi = elems.begin(); elemi != elems.end(); ++elemi)
				{
					U2VertexElement& elem = *elemi;
					VertexElementType currType = (elem.getType() == VET_COLOUR) ?
						srcType : elem.getType();
					if (elem.getType() == VET_COLOUR || 
						((elem.getType() == VET_COLOUR_ABGR || elem.getType() == VET_COLOUR_ARGB) 
						&& elem.getType() != destType))
					{
						u2uint32* pRGBA;
						elem.baseVertexPointerToElement(pBase, &pRGBA);
						U2VertexElement::convertColourValue(currType, destType, pRGBA);
					}
				}
				pBase = static_cast<void*>(
					static_cast<char*>(pBase) + bindi->second->getVertexSize());
			}
			bindi->second->unlock();

			// Modify the elements to reflect the changed type
			const U2VertexDeclaration::VertexElementList& allelems = 
				vertexDeclaration->getElements();
			U2VertexDeclaration::VertexElementList::const_iterator ai;
			unsigned short elemIndex = 0;
			for (ai = allelems.begin(); ai != allelems.end(); ++ai, ++elemIndex)
			{
				const U2VertexElement& elem = *ai;
				if (elem.getType() == VET_COLOUR || 
					((elem.getType() == VET_COLOUR_ABGR || elem.getType() == VET_COLOUR_ARGB) 
					&& elem.getType() != destType))
				{
					vertexDeclaration->modifyElement(elemIndex, 
						elem.getSource(), elem.getOffset(), destType, 
						elem.getSemantic(), elem.getIndex());
				}
			}

		}


	} // each buffer


}



//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
U2IndexData::U2IndexData()
{
	indexCount = 0;
	indexStart = 0;
	
}
//-----------------------------------------------------------------------
U2IndexData::~U2IndexData()
{
}
//-----------------------------------------------------------------------
U2IndexData* U2IndexData::clone(bool copyData, U2HardwareBufferManagerBase* mgr) const
{
	U2HardwareBufferManagerBase* pManager = mgr ? mgr : U2HardwareBufferManager::getSingletonPtr();
	U2IndexData* dest = U2_NEW U2IndexData();
	if (indexBuffer.get())
	{
        if (copyData)
        {
		    dest->indexBuffer = pManager->createIndexBuffer(indexBuffer->getType(), indexBuffer->getNumIndexes(),
			    indexBuffer->getUsage(), indexBuffer->hasShadowBuffer());
		    dest->indexBuffer->copyData(*indexBuffer, 0, 0, indexBuffer->getSizeInBytes(), true);
        }
        else
        {
            dest->indexBuffer = indexBuffer;
        }
    }
	dest->indexCount = indexCount;
	dest->indexStart = indexStart;
	return dest;
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
// Local Utility class for vertex cache optimizer
class Triangle
{
public:
	enum EdgeMatchType {
		AB, BC, CA, ANY, NONE
	};

	u2uint32 a, b, c;		

	inline Triangle()
	{
	}

	inline Triangle( u2uint32 ta, u2uint32 tb, u2uint32 tc ) 
		: a( ta ), b( tb ), c( tc )
	{
	}

	inline Triangle( u2uint32 t[3] )
		: a( t[0] ), b( t[1] ), c( t[2] )
	{
	}

	inline Triangle( const Triangle& t )
		: a( t.a ), b( t.b ), c( t.c )
	{
	}

	inline bool sharesEdge(const Triangle& t) const
	{
		return(	(a == t.a && b == t.c) ||
				(a == t.b && b == t.a) ||
				(a == t.c && b == t.b) ||
				(b == t.a && c == t.c) ||
				(b == t.b && c == t.a) ||
				(b == t.c && c == t.b) ||
				(c == t.a && a == t.c) ||
				(c == t.b && a == t.a) ||
				(c == t.c && a == t.b) );
	}

	inline bool sharesEdge(const u2uint32 ea, const u2uint32 eb, const Triangle& t) const
	{
		return(	(ea == t.a && eb == t.c) ||
				(ea == t.b && eb == t.a) ||
				(ea == t.c && eb == t.b) );	
	}

	inline bool sharesEdge(const EdgeMatchType edge, const Triangle& t) const
	{
		if (edge == AB)
			return sharesEdge(a, b, t);
		else if (edge == BC)
			return sharesEdge(b, c, t);
		else if (edge == CA)
			return sharesEdge(c, a, t);
		else
			return (edge == ANY) == sharesEdge(t);
	}

	inline EdgeMatchType endoSharedEdge(const Triangle& t) const
	{
		if (sharesEdge(a, b, t)) return AB;
		if (sharesEdge(b, c, t)) return BC;
		if (sharesEdge(c, a, t)) return CA;
		return NONE;
	}

	inline EdgeMatchType exoSharedEdge(const Triangle& t) const
	{
		return t.endoSharedEdge(*this);
	}

	inline void shiftClockwise()
	{
		u2uint32 t = a;
		a = c;
		c = b;
		b = t;
	}

	inline void shiftCounterClockwise()
	{
		u2uint32 t = a;
		a = b;
		b = c;
		c = t;
	}
};
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
void U2IndexData::optimiseVertexCacheTriList(void)
{
	if (indexBuffer->isLocked()) return;

	void *buffer = indexBuffer->lock(U2HardwareBuffer::HBL_NORMAL);

	Triangle* triangles;
	u2uint32 *dest;

	size_t nIndexes = indexCount;
	size_t nTriangles = nIndexes / 3;
	size_t i, j;
	u2uint16 *source = 0;

	if (indexBuffer->getType() == U2HardwareIndexBuffer::IT_16BIT)
	{
		triangles = U2_ALLOC_T(Triangle, nTriangles, MEMCATEGORY_GEOMETRY);
		source = (u2uint16 *)buffer;
		dest = (u2uint32 *)triangles;
		for (i = 0; i < nIndexes; ++i) dest[i] = source[i];
	}
	else
		triangles = (Triangle*)buffer;

	// sort triangles based on shared edges
	u2uint32 *destlist = U2_ALLOC_T(u2uint32, nTriangles, MEMCATEGORY_GEOMETRY);
	unsigned char *visited = U2_ALLOC_T(unsigned char, nTriangles, MEMCATEGORY_GEOMETRY);

	for (i = 0; i < nTriangles; ++i) visited[i] = 0;

	u2uint32 start = 0, ti = 0, destcount = 0;

	bool found = false;
	for (i = 0; i < nTriangles; ++i)
	{
		if (found)
			found = false;
		else
		{
			while (visited[start++]);
			ti = start - 1;
		}

		destlist[destcount++] = ti;
		visited[ti] = 1;

		for (j = start; j < nTriangles; ++j)
		{
			if (visited[j]) continue;
			
			if (triangles[ti].sharesEdge(triangles[j]))
			{
				found = true;
				ti = static_cast<u2uint32>(j);
				break;
			}
		}
	}

	if (indexBuffer->getType() == U2HardwareIndexBuffer::IT_16BIT)
	{
		// reorder the indexbuffer
		j = 0;
		for (i = 0; i < nTriangles; ++i)
		{
			Triangle *t = &triangles[destlist[i]];
            if(source)
            {
                source[j++] = (u2uint16)t->a;
                source[j++] = (u2uint16)t->b;
                source[j++] = (u2uint16)t->c;
            }
		}
		U2_FREE(triangles, MEMCATEGORY_GEOMETRY);
	}
	else
	{
		u2uint32 *reflist = U2_ALLOC_T(u2uint32, nTriangles, MEMCATEGORY_GEOMETRY);

		// fill the referencebuffer
		for (i = 0; i < nTriangles; ++i)
			reflist[destlist[i]] = static_cast<u2uint32>(i);
		
		// reorder the indexbuffer
		for (i = 0; i < nTriangles; ++i)
		{
			j = destlist[i];
			if (i == j) continue; // do not move triangle

			// swap triangles

			Triangle t = triangles[i];
			triangles[i] = triangles[j];
			triangles[j] = t;

			// change reference
			destlist[reflist[i]] = static_cast<u2uint32>(j);
			// destlist[i] = i; // not needed, it will not be used
		}

		U2_FREE(reflist, MEMCATEGORY_GEOMETRY);
	}

	U2_FREE(destlist, MEMCATEGORY_GEOMETRY);
	U2_FREE(visited, MEMCATEGORY_GEOMETRY);
				
	indexBuffer->unlock();
}
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
void U2VertexCacheProfiler::profile(const U2HardwareIndexBufferSharedPtr& indexBuffer)
{
	if (indexBuffer->isLocked()) return;

	u2uint16 *shortbuffer = (u2uint16 *)indexBuffer->lock(U2HardwareBuffer::HBL_READ_ONLY);

	if (indexBuffer->getType() == U2HardwareIndexBuffer::IT_16BIT)
		for (unsigned int i = 0; i < indexBuffer->getNumIndexes(); ++i)
			inCache(shortbuffer[i]);
	else
	{
		u2uint32 *buffer = (u2uint32 *)shortbuffer;
		for (unsigned int i = 0; i < indexBuffer->getNumIndexes(); ++i)
			inCache(buffer[i]);
	}

	indexBuffer->unlock();
}

//-----------------------------------------------------------------------
bool U2VertexCacheProfiler::inCache(unsigned int index)
{
	for (unsigned int i = 0; i < buffersize; ++i)
	{
		if (index == cache[i])
		{
			hit++;
			return true;
		}
	}

	miss++;
	cache[tail++] = index;
	tail %= size;

	if (buffersize < size) buffersize++;

	return false;
}



